
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
using System.Web;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Container;
using Swaf.Logging;
//using Swaf.Gui.AspNet2;

namespace Localization
{
    public class GoogleTranslationBizRules : Syrinx.BaseBizRules
	{
        protected static string s_url = "http://translate.google.com/translate_t?";
		protected static string s_apiUrl = "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair={0}&q={1}";
		protected static bool s_useApiUrl = true;
        protected static string s_containerStart = "<div id=result_box dir=\"ltr\">";
        protected static string s_containerEnd = "</div>";
		protected static Regex s_translation = new Regex("<div id=result_box dir=\"(ltr|rtl)\">(.*?)</div>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
		protected static Regex s_translation2 = new Regex(@"{""responseData"": {""translatedText"":""(.*?)""", RegexOptions.IgnoreCase | RegexOptions.Multiline);
		protected static int s_maxGoogleTranslationLength = 800;

		static Regex s_it1 = new Regex(@"\n.\s*╨\s*\n.", RegexOptions.Multiline);
		static Regex s_it2 = new Regex(@".\s*╧\s*.", RegexOptions.Multiline);
		static Regex s_it3 = new Regex(@".\s*╬\s*.", RegexOptions.Multiline);


		public void translateAllArticles(IApplication app, IRuleContext context, Swaf.BGuid.Guid translationId)
		{
			FlexiMap m = new FlexiMap();
			m.put("listDetailLevel", DetailLevel.idOnly);
			m.put("parentId", null);
			m.put("entireTree", true);
			IBizObjList articles = context.RuleGroup.execute(context, "getArticles", m, null).CallResults as IBizObjList;
			if (articles != null)
				for (int p = 0; p < articles.numElements; p++)
					context.RuleGroup.execute(context, "translateArticle", articles[p].KeyValue, translationId);

			m.put("BizObjType", "eComProduct");
			m.put("objectTreeName", "eComProd");
			articles = context.RuleGroup.execute(context, "getArticles", m, null).CallResults as IBizObjList;
			if (articles != null)
				for (int p = 0; p < articles.numElements; p++)
					context.RuleGroup.execute(context, "translateArticle", articles[p].KeyValue, translationId);
		}

		public static string s_sectionMarker = "[╬]";
		public static char s_sectionMarkerC = '╬';


		public void translateArticle(IApplication app, IRuleContext context, Swaf.BGuid.Guid articleId, Swaf.BGuid.Guid translationId)
        {
            try
            {
                Swaf.BGuid.Guid cmpId = ActiveCompanyId;
                IResults rc = app.ruleMgr.execute("getAllTranslations");
				if(rc.ExceptionInfo!=null)
					throw new LocalizationException("error executing updateArticleTranslation.", rc.ExceptionInfo);

				IBizObjList translations = rc.CallResults as IBizObjList;
				//getArticleDetail(IApplication app, IRuleContext context,object articleId,bool withTranslation,DetailLevel detailLevel, bool incrementCount, bool stripLeading)
				rc = context.RuleGroup.execute(context, "getArticleDetail", articleId, true, DetailLevel.fullDetails, false, true, null);
				IBizObj article = rc.CallResults as IBizObj;
                if (article == null)
                {
                    app.Log.println(LoggingLevels.High, "Cannot translate article with id {0}", articleId);
					article = context.RuleGroup.execute(context, "getArticleDetail", articleId, true, DetailLevel.fullDetails, false, true, null).CallResults as IBizObj;
                    return;
                }
				//IBizObj article = context.RuleGroup.execute(context, "getArticleDetail", articleId, true, DetailLevel.fullDetails, false, true).CallResults as IBizObj;
				string baseTitle = (string)article.get("baseTitle");
				string baseSummary = (string)article.get("baseSummary");
				string baseBody = (string)article.get("baseBody");
				string baseCaption = (string)article.get("baseCaption");
				StringBuilder buff = new StringBuilder(baseTitle.Length + baseSummary.Length + baseCaption.Length + 10);
				buff.Append(baseTitle).Append(s_sectionMarker).Append(baseSummary)
					.Append(s_sectionMarker).Append(baseBody).Append(s_sectionMarker).Append(baseCaption);
				string baseText = buff.ToString();
				app.Log.println(LoggingLevels.Medium, "Will Translate Article:{0}", baseTitle);
                for(int p = 0; p < translations.numElements; p++)
				{
                    IBizObj translation = translations[p];
                    if (translationId == null || translationId.CompareTo(translation.KeyValue) == 0)
                    {
                        string iso6391 = (string)translation.get("language.iso6391");
                        string langPair = "en|" + iso6391;
                        rc = context.RuleGroup.execute(context, "translateFromGoogle", baseText, false, langPair);
                        string translatedText = (string)rc.CallResults;
                        if (!Cmn.IsEmpty(translatedText))
                        {
                            translatedText = translatedText.Replace("&#9580;", "╬");
                            translatedText = s_it3.Replace(translatedText, "╬");
                            //Replace(s_sectionMarker, s_sectionMarkerC.ToString());
                            string[] pieces = translatedText.Split(s_sectionMarkerC);
                            app.Log.println(LoggingLevels.Medium, "		Pieces{0}:{1}", pieces.Length, pieces);
                            if (pieces.Length == 4)
                            {
                                article.put("titleTranslation", pieces[0]);
                                article.put("summaryTranslation", pieces[1]);
                                article.put("bodyTranslation", pieces[2]);
                                article.put("captionTranslation", pieces[3]);
                                rc = context.RuleGroup.execute(context, "updateArticleTranslation", article, translations[p].get("id"));
                            }
                        }
                        else
                            app.Log.println(LoggingLevels.Critical, "Failure to translate with google lang-pair en|{0} on {1}", translations[p - 1].get("language.iso6391"), baseTitle);
                    }
				}
            }
            catch (Exception e)
            {
                throw new LocalizationException("error executing translateArticle", e);
            }
        }

		//public void translateArticlePiece(IApplication app, IRuleContext context, string langPair, IBizObj article, string fieldName)
		//{
		//    List<string> htmlChunks = null;
		//    string contentToTranslate = article.get(fieldName).ToString();
		//    MatchCollection ms = s_htmlBlock.Matches(contentToTranslate);
		//    StringBuilder buff = new StringBuilder(contentToTranslate.Length);
		//    int lastpos = 0;
		//    if (ms.Count > 0)
		//    {
		//        htmlChunks = new List<string>();

		//        foreach (Match m in ms)
		//        {
		//            string p = contentToTranslate.Substring(lastpos, m.Index - lastpos);
		//            if (!s_allWhiteSpace.IsMatch(p))
		//            {
		//                buff.Append(p).Append("|");
		//                htmlChunks.Add(m.Value);
		//            }
		//            else
		//            {
		//                if (htmlChunks.Count == 0)
		//                {
		//                    buff.Append("|");
		//                    htmlChunks.Add(m.Value);
		//                }
		//                else
		//                    htmlChunks[htmlChunks.Count - 1] += m.Value;
		//            }
		//            lastpos = m.Index + m.Length;
		//        }
		//    }
		//    buff.Append(contentToTranslate.Substring(lastpos));

		//    IResults rc = app.ruleMgr.execute("translateFromGoogle", buff.ToString(), false, langPair);
		//    string translatedText = (string)rc.CallResults;
		//    string[] transpieces = translatedText.Split('|');
		//    buff = new StringBuilder(contentToTranslate.Length);
		//    lastpos = 0;
		//    foreach (string piece in transpieces)
		//    {
		//        buff.Append(piece);
		//        if(htmlChunks != null && lastpos < htmlChunks.Count)
		//            buff.Append(htmlChunks[lastpos++]);
		//    }
		//    translatedText = buff.ToString();

		//    if (fieldName == "baseBody")
		//        article.put("bodyTranslation", translatedText);
		//    else if (fieldName == "baseTitle")
		//        article.put("titleTranslation", translatedText);
		//    else if (fieldName == "baseSummary")
		//        article.put("summaryTranslation", translatedText);
		//}

		protected static Regex s_staticBreaker = new Regex("(.*?)<br id='br'/>");
		public void translateAllStaticTexts(IApplication app, IRuleContext context, Swaf.BGuid.Guid translationId)
		{
			IResults rc = app.ruleMgr.execute("getAllTranslations");
			if(rc.ExceptionInfo!=null)
				throw new LocalizationException("error executing updateArticleTranslation.", rc.ExceptionInfo);

			IBizObjList translations = (IBizObjList)rc.CallResults;
			string langPair = "";
			foreach (IBizObj translation in translations)
			{
				try
				{
					if (translationId == null || translationId.CompareTo(translation.KeyValue) == 0)
					{
						IRuleGroup rg = app.ruleMgr.executeAsync("getTextsForTranslation", null, "", new object[] { translation.get("id") });
						rg.waitForCompletion(true);
						IBizObjList texts = rg.CallResults as IBizObjList;
						if (texts != null)
						{
							StringBuilder buff = new StringBuilder(1024);
							string iso6391 = (string)translation.get("language.iso6391");
							langPair = "en|" + iso6391;
							app.Log.println(LoggingLevels.Medium, "\n------------------- Init static text translation {0} -------------------\n", langPair);

							foreach (IBizObj text in texts)
							{
								buff.Append(text.get("base.text")).Append("<br id='br'/>");
								rg.executeAsync(context, "translateFromGoogle", null, "", new object[] { text.get("base.text"), true, langPair });
							}
							rg.waitForCompletion(true);
							for (int pos = 1; pos < rg.numberOfResults; pos++)
							{
								string transTexts = rg.getCallResults(pos) as string;
								if (!Cmn.IsEmpty(transTexts))
								{
									IBizObj t = texts[pos-1];
									t.put("trans.text", transTexts);
									t.put("trans.translationId", translation.get("id"));
									app.Log.println(LoggingLevels.Medium, "update static text: {0}", t.Xml);
									context.RuleGroup.execute(context, "updateUiViewTranslationText", t);
								}
								else
									app.Log.println(LoggingLevels.Medium, "aborting a specific static text: {0}", texts[pos].get("base.text"));
							}
						}
					}
				}
				catch (Exception e)
				{
					app.Log.println(LoggingLevels.Medium, "Exception during static text processing {0}: {1}", langPair,  e);
				}
			}
		}

		protected Regex s_htmlBlock = new Regex(@"</?\w+((\s+\w+(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+))?)+\s*|\s*)/?>");
		protected Regex s_allWhiteSpace = new Regex(@"^\s*$");
		/// <summary>
        /// this rule will connect to http://translate.google.com/translate_t? and will
        /// try to translate the inputed text
        /// </summary>
        /// <param name="app"></param>
        /// <param name="content"></param>
        /// <param name="content"></param>
        /// <returns></returns>
		public string translateFromGoogle(IApplication app, IRuleContext context, string contentToTranslate, bool dummy, string langPair)
        {
            if (langPair == null || langPair == "")
                throw new LocalizationException("error executing translateText langPair parameter cannot be null or blank");
			//Step 1: break the content up based on embedded html blocks.  Google doesnt translate those too well.
			List<string> htmlChunks = null;
			MatchCollection ms = s_htmlBlock.Matches(contentToTranslate);
			StringBuilder buff = new StringBuilder(contentToTranslate.Length);
			StringBuilder translationData = new StringBuilder();
			int lastpos = 0;
			if (ms.Count > 0)
			{
				htmlChunks = new List<string>();
				int noTransStackDepth = 0;
				string matchNoTrans = "";
				string matchHtmlType = null;
				foreach (Match m in ms)
				{
					string p = contentToTranslate.Substring(lastpos, m.Index - lastpos);
					p = s_allWhiteSpace.IsMatch(p) ? "" : p;
					if (noTransStackDepth == 0 && !Cmn.IsEmpty(p))
					{
						buff.Append(p).Append("\r\n╨\r\n");
						htmlChunks.Add(m.Value);
					}
					else
					{
						if (htmlChunks.Count == 0)
						{
							buff.Append("\r\n╨\r\n");
							htmlChunks.Add(p + m.Value);
						}
						else
							htmlChunks[htmlChunks.Count - 1] += p + m.Value;
					}
					lastpos = m.Index + m.Length;

					if (m.Groups[0].Value.StartsWith("<div", StringComparison.CurrentCultureIgnoreCase) ||
						m.Groups[0].Value.StartsWith("<span", StringComparison.CurrentCultureIgnoreCase) ||
                        m.Groups[0].Value.StartsWith("<pre", StringComparison.CurrentCultureIgnoreCase) ||
						m.Groups[0].Value.StartsWith("<style", StringComparison.CurrentCultureIgnoreCase))
					{
						if (noTransStackDepth == 0)
						{
							matchHtmlType = "<" + m.Groups[0].Value.Substring(1, 3);
							matchNoTrans = "</" + m.Groups[0].Value.Substring(1, 3);
							for (int pos = 1; pos < m.Groups.Count; pos++)
							{
								string v = m.Groups[pos].Value.Trim().Replace("\"", "");
								if (string.Compare(v, "class=code", true) == 0 ||
									string.Compare(v, "class=ntrans", true) == 0 ||
									m.Groups[0].Value.StartsWith("<style", StringComparison.CurrentCultureIgnoreCase) ||
                                    m.Groups[0].Value.StartsWith("<pre", StringComparison.CurrentCultureIgnoreCase))
								{
									noTransStackDepth++;
									break;
								}
							}
						}
						else if (m.Groups[0].Value.StartsWith(matchHtmlType))
							noTransStackDepth++;
					}
					else if (noTransStackDepth > 0 && m.Groups[0].Value.StartsWith(matchNoTrans, StringComparison.CurrentCultureIgnoreCase))
						noTransStackDepth--;
					else if (noTransStackDepth > 0 && (m.Groups[0].Value.StartsWith("<div", StringComparison.CurrentCultureIgnoreCase) ||
						m.Groups[0].Value.StartsWith("<span", StringComparison.CurrentCultureIgnoreCase) ||
						m.Groups[0].Value.StartsWith("<style", StringComparison.CurrentCultureIgnoreCase)))
						noTransStackDepth++;
					if (buff.Length > s_maxGoogleTranslationLength)
					{
						translationData.Append(doTranslation(app, langPair, buff.ToString()));
						buff = new StringBuilder();
					}
				}
			}
			buff.Append(contentToTranslate.Substring(lastpos));
			translationData.Append(doTranslation(app, langPair, buff.ToString()));


			string responseFromServer = "";
			//Step 3: recombine the translated sections with the html blocks.
			if (translationData.Length > 0)
			{
				translationData.Replace("&#9576;", "╨").Replace(@"\u0026", "&");
				responseFromServer = translationData.ToString();
				responseFromServer = s_it1.Replace(responseFromServer, "╨");
				string[] transpieces = responseFromServer.Split('╨');
				buff = new StringBuilder(responseFromServer.Length);
				lastpos = 0;
				foreach (string piece in transpieces)
				{
					buff.Append(piece);
					if (htmlChunks != null && lastpos < htmlChunks.Count)
						buff.Append(htmlChunks[lastpos++]);
				}
				responseFromServer = buff.ToString();
			}

			return responseFromServer;
        }

		protected string doTranslation(IApplication app, string langPair, string textToTrans)
		{
			string responseFromServer = null;
			//Step 2: Do the Translation through google
			Stream dataStream = null;
			StreamReader reader = null;
			WebResponse response = null;
			try
			{
				if (s_useApiUrl)
				{
					//string data = HttpUtility.UrlEncode(buff.ToString());
					//string url = string.Format("{0}?v=1.0&q={1}&langpair={2}", s_apiUrl, data, langPair.Replace("|", "%7C"));
					//HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
					//request.Referer = "http://syrinx.ph";
					//request.Method = "GET";
					//request.KeepAlive = false;
					//response = request.GetResponse();
					//dataStream = response.GetResponseStream();
					//reader = new StreamReader(dataStream);
					//responseFromServer = reader.ReadToEnd();

					string data = HttpUtility.UrlEncode(textToTrans);
					string dataURL = string.Format(s_apiUrl, langPair, data);
					HttpWebRequest request = (HttpWebRequest)WebRequest.Create(dataURL);
					request.Method = "GET";
					request.KeepAlive = false;

//					string postData = string.Format("v=1.0&langpair={0}&q={1}", langPair, data);
					//byte[] byteArray = Encoding.UTF8.GetBytes(postData);
					//request.ContentType = "application/x-www-form-urlencoded";
					//request.ContentLength = byteArray.Length;
					//dataStream = request.GetRequestStream();
					//dataStream.Write(byteArray, 0, byteArray.Length);

					response = request.GetResponse();
					dataStream = response.GetResponseStream();
					string[] types = response.ContentType.Split(';');

					if (types.Length > 1)
					{
						string type = response.ContentType.Split(';')[1].Split('=')[1];
						reader = new StreamReader(dataStream, Encoding.GetEncoding(type));
					}
					else
						reader = new StreamReader(dataStream);
					responseFromServer = reader.ReadToEnd();
					app.Log.println(LoggingLevels.Medium, "	google response {0}:{1}", langPair, responseFromServer);
					Match m = s_translation2.Match(responseFromServer);
					if (m.Success)
					{
						responseFromServer = m.Groups[1].Value;
						responseFromServer = app.resMgr.fixupFromXml(responseFromServer);
					}
					else
					{
						app.Log.println(LoggingLevels.Medium, "	google response was not parsable");
						responseFromServer = "";
					}
				}
				else
				{
					HttpWebRequest request = (HttpWebRequest)WebRequest.Create(s_url);
					request.Method = "GET";
					request.KeepAlive = false;
					string data = HttpUtility.UrlEncode(textToTrans.ToString());

					string postData = string.Format("ie=UTF8&hl=en&langpair={0}&text={1}", langPair, data);

					byte[] byteArray = Encoding.UTF8.GetBytes(postData);
					request.ContentType = "application/x-www-form-urlencoded";
					request.ContentLength = byteArray.Length;
					dataStream = request.GetRequestStream();
					dataStream.Write(byteArray, 0, byteArray.Length);
					response = request.GetResponse();
					dataStream = response.GetResponseStream();
					string[] types = response.ContentType.Split(';');

					if (types.Length > 1)
					{
						string type = response.ContentType.Split(';')[1].Split('=')[1];
						reader = new StreamReader(dataStream, Encoding.GetEncoding(type));
					}
					else
						reader = new StreamReader(dataStream);
					responseFromServer = reader.ReadToEnd();
					app.Log.println(LoggingLevels.Medium, "	google response {0}:{1}", langPair, responseFromServer);
					Match m = s_translation.Match(responseFromServer);
					if (m.Success)
					{
						responseFromServer = m.Groups[2].Value;
						responseFromServer = app.resMgr.fixupFromXml(responseFromServer);
					}
					else
					{
						app.Log.println(LoggingLevels.Medium, "	google response was not parsable");
						responseFromServer = "";
					}
				}
			}
			catch (Exception e)
			{
				throw new LocalizationException(string.Format("error executing translateText with length of {0}: Text was {1}", textToTrans.Length, textToTrans), e);
			}
			finally
			{
				// Close the Stream object.
				dataStream.Close();
				dataStream.Dispose();
				reader.Close();
				reader.Dispose();
				response.Close();
			}

			return responseFromServer;
		}
    }
}